<template>
  <div v-if="isShow" ref="varTreeNodeRow" class="var-tree-node">
    <div class="_vti-line" :class="{ _root: isRoot }" :style="{ left: lineLeft + 'px' }"></div>
    <div
      v-if="isSingle"
      :style="{ marginLeft: marginLeftAtParent + 'px' }"
      class="_single-vt-line">
    </div>

    <div class="g-jc-sb node-content">
      <!-- left -->
      <div class="_variable-name" :style="{ paddingLeft: marginLeftAtParent + 'px' }">
        <span class="g-relative g-one-line" @contextmenu="handleContextMenu" @click="handleClick">
          <span v-if="nodeData?.hasChildren" class="g-pointer" @click="handleToggleExpand">
            <div class="_rect">
              <div class="_rect-1"></div>
              <div v-if="!nodeData?.expand" class="_rect-2"></div>
            </div>
          </span>
          <span v-else class="_hr-line _empty-line"></span>
          <span class="_hr-line" />
          <img
            :src="nodeData.icon"
            alt="icon"
            style="height:16px;width:16px;"
            class="_tree-node-icon" />
          <span class="show-node-text" :class="{selected: nodeData?.isSelected}">
            <span class="node-name">{{ nodeData?.name }}</span>
            <span class="node-expression">{{ nodeData?.expression }}</span>
          </span>
        </span>
      </div>
      <!-- right -->
      <div
        :key="nodeData?.valueId"
        :title="nodeData?.constraintMessage"
        class="_variable-value"
        :class="`${nodeData?.status}`">
        <div class="rightLine" :class="{ indragging: TreeNodeDragItem.indragging }" @mousedown="handleMouseDown($event)"></div>
        <span class="value_comp">
          <el-checkbox
            v-if="nodeData?.valueType === 'Boolean'"
            v-model="booleanValue"
            true-label="true"
            false-label="false"
            size="small"
            :class="{
              'radio-position': true,
              'red-border': invalidValue,
              'red-bg': nodeData?.status === 'red',
              'green-bg': nodeData?.status === 'green',
              'white-bg': nodeData?.status === 'white'
            }"
            :disabled="!['Integer', 'Real', 'Boolean', 'String'].includes(nodeData?.valueType)"
            @change="updateVariableValue(booleanValue)">
            {{ nodeData.value }}
          </el-checkbox>
          <el-select
            v-else-if="nodeData?.valueType === 'Enumeration'"
            v-model="nodeData.value"
            class="m-2"
            placeholder="Select"
            size="small"
            @change="updateVariableValue(nodeData.value)">
            <el-option v-for="(item, index) in selectOptions" :key="index" :label="item.label" :value="item.value" />
          </el-select>

          <el-input
            v-else
            v-model="nodeData.value"
            :disabled="nodeData?.disabled"
            size="small"
            :class="{
              'red-border': invalidValue,
              'red-bg': nodeData?.status === 'red',
              'green-bg': nodeData?.status === 'green',
              'white-bg': nodeData?.status === 'white'
            }"
            @focus="variableInputFocus(nodeData.value)"
            @blur="updateVariableValue(nodeData.value)"
            @keyup.enter="updateVariableValue(nodeData.value)">
            {{ nodeData?.value }} {{ nodeData?.modelVo.type }}
          </el-input>
        </span>
      </div>
    </div>
    <!-- iterate -->
    <div v-show="nodeData?.children && nodeData?.expand" class="_sub-node-wrapper">
      <VariableNode
        v-for="item in nodeData?.children"
        :key="item.valueId"
        :nodeData="item"
        :level="level + 1"
        :marginLeftAtParent='marginLeftAtParent + 21'
        :isSingle="nodeData?.children?.length === 1" />
    </div>

  </div>
</template>
<script lang="ts" setup>
import { controller } from "@/engine/Controller";
import { ContextMenuConfig, VariableData } from "@/engine/_";
import ContextMenu from "@/common/ContextMenu/index.vue";
import { ref, computed, inject, onMounted, reactive, watch } from "vue";
import { API } from "@/constants/api";
import { TreeNodeDrag } from "@/engine/types/variable";
import { modelHttp } from "@/util/httpClient/ModelNodeHttpClient";
import { ipcRenderer } from "electron";
import { IpcMsg } from "@/engine/types/Ipc";
import { SimFrameToSubChannel, SubWinName } from "@/engine/types/Win";
import { TimeSeryMountedStorageData } from "@/engine/types/charts";

import { HiddenStatus } from "@/engine/types/Controller";
import { EnumerationValue } from "@/engine/types/variable";
import { PropertyKey } from "@/constants/constants";
const props = defineProps({
  nodeData: {
    type: Object,
    default() {
      return {};
    }
  },
  level: {
    type: Number,
    default: 1
  },
  isSingle: {
    type: Boolean
  },
  isRoot: Boolean,
  marginLeftAtParent: {
    type: Number,
    default: 0 // 秒
  }
});

const isShow = computed(() => {
  if (props.nodeData?.modelVo.hiddenStatus === HiddenStatus.HIDDEN_IN_ENCAP) return false;
  return true;
});

let booleanValue = ref(props.nodeData.value === "true");

const selectOptions = reactive<EnumerationValue[]>([]);
const invalidValue = ref(false);
let nodeDataOldValue = props.nodeData.value as string;

const TreeNodeDragItem = inject('TreeNodeDragItem') as TreeNodeDrag;
const currentWidth = computed(() => {
  return TreeNodeDragItem.getCurrentWidth;
});

const varTreeNodeRow = ref();

const handleMouseDown = (event: MouseEvent) => {
  TreeNodeDragItem.mouseStartX = event.clientX;
  TreeNodeDragItem.indragging = true;
  window.addEventListener('mousemove', handleMouseMove);
  window.addEventListener('mouseup', handleMouseUp);
};

const handleMouseMove = (event: any) => {
  const cha = event.clientX - TreeNodeDragItem.mouseStartX;
  const test = (cha / varTreeNodeRow.value.offsetWidth) * 100;
  let value = TreeNodeDragItem.currentWidth - test;
  TreeNodeDragItem.changeCurrentWidth(value);
  TreeNodeDragItem.mouseStartX = event.clientX;
};

const handleMouseUp = () => {
  TreeNodeDragItem.indragging = false;
  window.removeEventListener('mousemove', handleMouseMove);
  window.removeEventListener('mouseup', handleMouseUp);
};

const variableMethodMap: {[key: string]: () => void } = {
  exportToNew: () => {
    controller.variableService.exportToNew(props.nodeData.valueId, props.nodeData.isRoot);
  },
  exportToExisted: () => {
    controller.variableService.exportAtExisted(props.nodeData?.modelId, props.nodeData?.valueId);
  },
  exportInteraction: async () => {
    const { data } = await modelHttp.get({
      api: API.QUERY_MODEL_INFO,
      data: {
        projectId: controller.projectId,
        modelId: props.nodeData.modelVo.id
      }
    });
    if (data?.data.ownerId) {
      ipcRenderer.send(IpcMsg.MessageChannel, {
        windowName: 'MainWindow',
        msg: {
          type: 'SimCreateInteraction',
          data: {
            modelId: props.nodeData.modelVo.id,
            modelName: data.data.name,
            ownerId: data.data.ownerId,
            metaclass: data.data.metaclass,
            baseSt: data.data.baseSt,
            sts: data.data.sts,
          }
        }
      });
    }
  },
  removeValue: async () => {
    await controller.simApi.variableRemove({
      simulationSessionId: controller.sessionId,
      sceneId: controller.sceneId,
      valueId: props.nodeData.valueId,
      index: props.nodeData.mindex || 0,
    });
  },
  addValue: async () => {
    await controller.variableService.updateVariable({
      valueId: props.nodeData.valueId,
      ov: nodeDataOldValue,
      index: props.nodeData.mindex
    });
  },
  openTimeSeriesChart: () => {
    controller.chartsService.openChartWinDirectly(
      props.nodeData.valueId,
      props.nodeData?.modelVo.name,
      (data: TimeSeryMountedStorageData) => {
        if (controller.chartsService.timeSeryService.updateByVariablePanelTimer) {
          clearInterval(controller.chartsService.timeSeryService.updateByVariablePanelTimer);
          controller.chartsService.timeSeryService.timesRecord = 0;
        }
        const propDataInfo = controller.variableService.variableDataMap.get(props.nodeData.valueId);
        controller.winService.sendInfoToSubWins(
          {
            type: SimFrameToSubChannel.UpdateChartsData,
            data: {
              isDirect: true,
              statusinfo: {
                constraintMessage: propDataInfo?.constraintMessage,
                status: propDataInfo?.status
              },
              values: [
                controller.chartsService.timeSeryService.timesRecord,
                propDataInfo?.value || 0
              ]
            }
          },
          SubWinName.Charts
        );
        controller.chartsService.timeSeryService.updateByVariablePanelTimer = window.setInterval(() => {
          const propDataInfo = controller.variableService.variableDataMap.get(props.nodeData.valueId);
          controller.chartsService.timeSeryService.timesRecord += 1000;
          controller.winService.sendInfoToSubWins(
            {
              type: SimFrameToSubChannel.UpdateChartsData,
              data: {
                statusinfo: {
                  constraintMessage: propDataInfo?.constraintMessage,
                  status: propDataInfo?.status
                },
                values: [
                  controller.chartsService.timeSeryService.timesRecord,
                  propDataInfo?.value || 0
                ]
              }
            },
            SubWinName.Charts
          );
        }, 1000);
      }
    );
  }
};

function handleToggleExpand() {
  props.nodeData.expand = !props.nodeData.expand;
  if (props.nodeData.expand) {
    controller.variableService.variableDataExpand(props.nodeData as VariableData);
    // record the expand node
    controller.variableService.expandVariableRecord.add(props.nodeData.valueId);
  } else {
    controller.variableService.expandVariableRecord.delete(props.nodeData.valueId);
  }
}

function handleClick() {
  controller.variableService.selectedValueChange(props.nodeData.valueId);
}

async function handleContextMenu(event: MouseEvent) {
  controller.variableService.selectedValueChange(props.nodeData.valueId);
  let contextMenuItems = [];
  contextMenuItems.push(ContextMenuConfig.exportInstance);

  // handle add|remove
  if (props.nodeData?.value) { // filter partProperty
    contextMenuItems.push(ContextMenuConfig.removeValue);
  } else {
    contextMenuItems.push(ContextMenuConfig.addValue);
  }
  const addRemoveValueFlag = props.nodeData?.modelVo.baseSt && props.nodeData?.modelVo.baseSt.includes('PartProperty');
  ContextMenuConfig.addValue.disabled = addRemoveValueFlag;
  ContextMenuConfig.removeValue.disabled = addRemoveValueFlag;

  // openTimeSeriesChart
  if (["Real", "Integer"].includes(props.nodeData?.valueType)) {
    contextMenuItems.push(ContextMenuConfig.openTimeSeriesChart);
  }

  const isSupportExportInteractionRes = await controller.modelApi.modelCheckElementSupportEmulation({
    projectId: controller.projectId,
    modelId: props.nodeData?.modelVo.id
  });
  if (isSupportExportInteractionRes.data?.data) {
    contextMenuItems.push(ContextMenuConfig.exportInteraction);
  }

  if (props.nodeData?.modelVo?.baseSt.includes("PartProperty")) {
    const modelinfo = await controller.modelApi.modelPropertyByKey({
      projectId: controller.projectId,
      modelId: props.nodeData?.modelVo.id,
      key: PropertyKey.TypedElement_type
    });
    ContextMenuConfig.exportInteraction.disabled = !modelinfo?.value ? true : false;
    contextMenuItems.push(ContextMenuConfig.exportInteraction);
  }

  let menuX = 0;
  if (contextMenuItems.length && event.clientY + contextMenuItems.length * 40 < document.body.clientHeight) {
    menuX = event.clientY;
  } else {
    menuX = document.body.clientHeight - contextMenuItems.length * 40;
  }
  ContextMenu.popup({
    x: event.clientX,
    y: menuX,
    menuItems: contextMenuItems
  })
    .then((menuItem: any) => {
      console.info('menu excute: ', menuItem.method);
      variableMethodMap[menuItem.method] && variableMethodMap[menuItem.method]();
    })
    .catch((err: any) => err && console.error(err));
}

const lineLeft = computed(() => {
  return props.level > 2 ? props.level * 20 : 37;
});

const variableInputFocus = (value: string) => {
  if (!value) {
    variableMethodMap.addValue();
  }
};

const updateVariableValue = async (value: string | boolean) => {
  if (String(value) !== nodeDataOldValue) {
    let useValueId = props.nodeData.valueId;
    if (useValueId.includes('@@')) {
      useValueId = useValueId.split('@@')[0];
    }
    let isUpdateSuccess;
    if (String(value) && useValueId) {
      isUpdateSuccess = await controller.variableService.updateVariable({ valueId: useValueId, index: props.nodeData.mindex, value, ov: nodeDataOldValue });
    } else {
      isUpdateSuccess = await controller.simApi.variableRemove({
        simulationSessionId: controller.sessionId,
        sceneId: controller.sceneId,
        valueId: useValueId, 
        index: props.nodeData.mindex
      });
    }
    
    if (isUpdateSuccess) {
      nodeDataOldValue = value as string;
    }
  }
};

watch(
  () => props.nodeData.value,
  (v) => {
    // extensive for checkbox model
    if (props.nodeData?.valueType === 'Boolean') {
      booleanValue.value = v === 'true';
      nodeDataOldValue = v;
    }
  }
);

onMounted(async () => {
  if (props.nodeData.valueType === 'Enumeration') {
    console.log(props.nodeData.value, 'props.nodeData.valueEnumeration');
    selectOptions.push(...await controller.variableService.getEnumerationDefaultAndItems(props.nodeData.value));
  }
});

</script>
<style lang="scss">
.var-tree-node {
  $currentWidth:v-bind(currentWidth);
  position: relative;
  line-height: 24px;

  ._variable-name {
    display: inline-block;
    box-sizing: border-box;
    width: calc(100% - $currentWidth);
    overflow: hidden;
  }

  .node-content {
    border-bottom: 1px solid #dcd9d6;
    padding-left: 10px;
  }

  ._variable-value {
    flex:1;
    box-sizing: border-box;
    display: inline-block;
    height: 24px;
    line-height: 24px;
    white-space: nowrap;
    width: $currentWidth;
    border-left: 1px solid #dcd9d6;
    position: relative;
    .rightLine{
      position: absolute;
      left: -2px;
      height: 25px;
      width: 3px;
      top: 0px;
      background-color: #dcd9d6;
      z-index:999;
      opacity: 0;
    }
    .indragging{
      background-color: #A2B3CD;
        opacity: 1;
    }
    .value_comp {
      width: 100%;
      position: absolute;
      z-index: 99;
    }

    .el-select{
      width: 97%;
    }
    .el-input{
      .el-input__inner{
        border: none;
        width: 100%;
      }
    }
    .is-focus{
      .el-input__inner{
        border: 1px solid #409eff;
        width: 100%;
      }
    }
  }

  // height: 24px;
  .g-one-line {
    padding-right: 100%;
  }

  .node-name {
    margin-left: 4px;
    display: inline-block;
    // width:200px;
  }

  .node-expression {
    padding: 4px;
    color: rgba($color: #000000, $alpha: 0.45);
  }

  .show-node-text {
    display: inline-block;
  }

  .show-node-text.selected {
    background-color: #3483b8 !important;
    color: white;
  }

  .node-value {
    display: inline-block;
    position: absolute;
    left: 200px;
    width: 200px;
  }

  ._sub-node-wrapper {
    position: relative;
  }

  >.var-tree-node {
    padding-left: 27px;
  }

  &:last-of-type {
    &>._vti-line {
      display: none;
    }
  }

  &>._vti-line {
    height: calc(100% + 18px);
    border-left: 1px solid black;
    position: absolute;
    top: -4px;
    &._root {
      height: calc(100% + 1px);
      top: 9px;
    }
  }

  &>._single-vt-line {
    height: 16px;
    border-left: 1px solid black;
    position: absolute;
    top: -4px;
    left: 15.5px;
  }

  ._hr-line {
    display: inline-block;
    width: 10px;
    height: 1px;
    border-top: 1px solid black;
    position: relative;
    top: -2px;

    &._empty-line {
      margin-left: 5.5px;
      width: 5px;
    }
  }

  ._rect {
    width: 11px;
    height: 11px;
    box-sizing: border-box;
    border: 1px solid black;
    position: relative;
    display: inline-block;
    background: white;
    top: 2px;

    ._rect-1 {
      position: absolute;
      width: 5px;
      border-top: 1px solid black;
      left: 2px;
      top: 4px;
    }

    ._rect-2 {
      position: absolute;
      height: 5px;
      border-left: 1px solid black;
      left: 4px;
      top: 2px;
    }
  }
  .showInputValue {
      cursor: pointer;
      display: inline-block;
      width: 100%;
      height: 24px;
      position: absolute;
      background-color: white;
      z-index: 999;
      font-size: 13px;
      color: #606266;
      padding-left: 3px;
    }
  .radio-position {
    margin-left: 10px;
  }

  .red-border {
    border-color: rgb(248, 226, 221);
  }

  .red-bg {
    background-color: rgb(248, 226, 221);
    .el-input__inner {
      background-color: rgb(248, 226, 221);
    }
  }

  .green-bg {
    background-color: rgb(225, 244, 215);
    .el-input__inner {
      background-color: rgb(225, 244, 215);
    }
  }

  .white-bg {
    background-color: white;

    .el-input__inner {
      background-color: white;
    }
  }

  .el-input__inner {
    height: 24px;
  }

  .el-checkbox {
    height: 16px;
    margin-top: 4px;
  }

}
</style>
